﻿using System;
using CI.Organism;
using NUnit.Framework;
using Rhino.Mocks;
using CI.Interfaces;
// ReSharper disable InconsistentNaming

namespace Mesopotamia.UnitTests.Neural
{
    [TestFixture]
    public class UseItorLoseItTests
    {
        private ILearningRule _useItorLoseIt;
        private ISynapse _synapse;
        private INeuralSystem _sns;
        private MockRepository _mocks;
        private const double _gainLearningRate = 1;
        private const double _loseLearningRate = 2;
        private const short _inputWeight = 100;

        [SetUp]
        public void Setup()
        {
            _mocks = new MockRepository();
            _sns = _mocks.DynamicMock<INeuralSystem>();
            _synapse = new SimpleSynapse(Guid.NewGuid(), _sns);
        }

        /// <summary>This proceedure makes sure the synapse value is not null</summary>
        [Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "synapse")]
        public void UseItOrLoseIt_CtorNullSynapse_Exception()
        {
            _useItorLoseIt = new UseItOrLoseIt(0, 0, null, Guid.NewGuid());
        }

        /// <summary>This proceedure makes sure the guid value is not empty</summary>
        [Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "guid")]
        public void UseItOrLoseIt_CtorEmptyGuid_Exception()
        {
            _useItorLoseIt = new UseItOrLoseIt(0, 0, null, Guid.Empty);
        }

        /// <summary>
        /// A neuron fires and the synapse connected to it is active and so should increase its weight
        /// </summary>
        [Test]
        public void TargetNeuronFired_ActiveWeightIncrease_Equal()
        {
            // create the learning rule
            CreateAndInit(_inputWeight);

            // simulate the synapse getting loaded with a output value
            _synapse.FireInput(100);

            // simulate the neuron calling notifiying learning rule that it fired
            _useItorLoseIt.TargetNeuronFired(true);

            // check the connection weight to see if its adjusted to what we expected
            Assert.AreEqual(_inputWeight + _gainLearningRate, _synapse.InputWeight);
        }

        private void CreateAndInit(double initWeight)
        {
            _useItorLoseIt = new UseItOrLoseIt(_gainLearningRate, _loseLearningRate, _synapse, Guid.NewGuid());

            // set the synapse weight
            _synapse.InitInputWeight = initWeight;
        }

        /// <summary>
        /// A neuron fires and the synapse connected to it is not active and so should decrease its weight
        /// </summary>
        [Test]
        public void TargetNeuronFired_ActiveWeightDecrease_Equal()
        {
            // create the learning rule
            CreateAndInit(_inputWeight);

            // simulate the neuron calling notifiying learning rule that it fired
            _useItorLoseIt.TargetNeuronFired(false);

            // check the connection weight to see if its adjusted to what we expected
            Assert.AreEqual(_inputWeight - _loseLearningRate, _synapse.InputWeight);
        }

        [Test]
        public void TargetNeuronFired_ReboundFromZeroPositive_Equal()
        {
            const int sign = 1;

            CheckToZeroAndBackUpAgain(sign);
        }

        [Test]
        public void TargetNeuronFired_ReboundFromZeroNegative_Equal()
        {
            const int sign = -1;

            CheckToZeroAndBackUpAgain(sign);
        }

        private void CheckToZeroAndBackUpAgain(int sign)
        {
            CreateAndInit(_inputWeight * sign);

            // take it to zero
            TakeItToZero();

            Assert.AreEqual(0d, _synapse.InputWeight);

            _synapse.FireInput(100);
            _useItorLoseIt.TargetNeuronFired(true);

            Assert.AreEqual(_gainLearningRate * sign, _synapse.InputWeight);
        }

        [Test]
        public void TargetNeuronFired_CantGoFromPositiveToNegative_Equal()
        {
            const int sign = 1;

            // create the learning rule
            CheckSignTheSame(sign);
        }

        [Test]
        public void TargetNeuronFired_CantGoFromNegativeToPositive_Equal()
        {
            const int sign = -1;

            // create the learning rule
            CheckSignTheSame(sign);
        }

        private void CheckSignTheSame(int sign)
        {
            CreateAndInit(_inputWeight * sign);

            // take it to zero
            TakeItToZero();

            Assert.AreNotEqual(-1 * sign, Math.Sign(_synapse.InputWeight));

            // fire one more time and make sure its still not negative
            _useItorLoseIt.TargetNeuronFired(false);
            Assert.AreNotEqual(-1 * sign, Math.Sign(_synapse.InputWeight));
        }

        private void TakeItToZero()
        {
            const double FireCount = _inputWeight / _loseLearningRate;

            for (int i = 0; i < FireCount; i++)
            {
                _useItorLoseIt.TargetNeuronFired(false);
            }
        }

        /// <summary>test null synapse being passed to it</summary>
        [Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "synapse")]
        public void CreateNewLearningRule_NullSynapse_Exception()
        {
            LearningRule.CreateNewLearningRule(0, null);
        }

        /// <summary>test to see if it created a new learning rule</summary>
        [Test]
        public void CreateNewLearningRule_Created_NotNull()
        {
            Assert.IsNotNull(LearningRule.CreateNewLearningRule(0, _synapse), "New syanpse not created");
        }
    }
}
